#include "asm.h"
#include "memlayout.h"
#include "mmu.h"

# Start the first CPU: switch to 32-bit protected mode, jump into C.
# The BIOS loads this code from the first sector of the hard disk into
# memory at physical address 0x7c00 and starts executing in real mode
# with %cs=0 %ip=7c00.

.code16                       # Assemble for 16-bit mode
.globl start                  # 声明start对外开放，这样Makefile中才可以使用
start:
  cli                         # BIOS enabled interrupts; disable

  # Zero data segment registers DS, ES, and SS. （cs在执行代码时会被用到，所以不能被修改）
  xorw    %ax,%ax             # Set %ax to zero
  movw    %ax,%ds             # -> Data Segment
  movw    %ax,%es             # -> Extra Segment
  movw    %ax,%ss             # -> Stack Segment

  # Physical address line A20 is tied to zero so that the first PCs 
  # with 2 MB would run software that assumed 1 MB.  Undo that.
  # 有关A20更多信息，请看下面的链接：
  # https://wiki.osdev.org/A20_Line
  # 有关seta20.1和seta20.2代码是什么意思，请看下面的链接：
  # https://www.win.tue.nl/~aeb/linux/kbd/A20.html
  # https://wiki.osdev.org/I/O_Ports
  # https://wiki.osdev.org/"8042"_PS/2_Controller
seta20.1:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.1

  movb    $0xd1,%al               # 0xd1 -> port 0x64
  outb    %al,$0x64

seta20.2:
  inb     $0x64,%al               # Wait for not busy
  testb   $0x2,%al
  jnz     seta20.2

  movb    $0xdf,%al               # 0xdf -> port 0x60
  outb    %al,$0x60

  # Switch from real to protected mode.  Use a bootstrap GDT that makes
  # virtual addresses map directly to physical addresses so that the
  # effective memory map doesn't change during the transition.
  lgdt    gdtdesc                 # 初始化gdt
  movl    %cr0, %eax
  orl     $CR0_PE, %eax           # 开启保护模式（cr0寄存器字段描述请看：https://wiki.osdev.org/CPU_Registers_x86）
  movl    %eax, %cr0

//PAGEBREAK!
  # Complete the transition to 32-bit protected mode by using a long jmp
  # to reload %cs and %eip.  The segment descriptors are set up with no
  # translation, so that the mapping is still the identity mapping.
  # SEG_KCODE值为1，表示引用gdt中下标为1的segment descriptor，即内核代码
  # 左移3位的原因是segment selector的低3位要用于表示其他意义
  # 有关segment selector的具体格式请看intel开发手册卷3的3.4.2部分
  ljmp    $(SEG_KCODE<<3), $start32

.code32  # Tell assembler to generate 32-bit code now.
start32:
  # Set up the protected-mode data segment registers
  movw    $(SEG_KDATA<<3), %ax    # Our data segment selector，SEG_KDATA值为2，表示指向gdt下标为2的segment descriptor
  movw    %ax, %ds                # -> DS: Data Segment
  movw    %ax, %es                # -> ES: Extra Segment
  movw    %ax, %ss                # -> SS: Stack Segment
  movw    $0, %ax                 # Zero segments not ready for use，gdt的0下标指向的数据为null
  movw    %ax, %fs                # -> FS
  movw    %ax, %gs                # -> GS

  # Set up the stack pointer and call into C.
  # start为该代码的起始地址，即0x7c00
  # 下面将start值放入%esp中用来设置堆栈起始地址
  # 因为堆栈是向下增长的，所以说内存区域0x7c00地址往下是堆栈区，0x7c00地址往上是代码区
  movl    $start, %esp

  # 调用bootmain.c中的bootmain方法
  call    bootmain

  # 下面的逻辑不应该被执行，除非代码bug了
  # If bootmain returns (it shouldn't), trigger a Bochs
  # breakpoint if running under Bochs, then loop.
  movw    $0x8a00, %ax            # 0x8a00 -> port 0x8a00
  movw    %ax, %dx
  outw    %ax, %dx
  movw    $0x8ae0, %ax            # 0x8ae0 -> port 0x8a00
  outw    %ax, %dx
spin:
  jmp     spin

# Bootstrap GDT
# 有关gdt和gdtdesc的详细格式说明，请阅读 Intel® 64 and IA-32 Architectures Software Developer’s Manual 的第3章的第3.4和3.5节
# 地址为：https://software.intel.com/sites/default/files/managed/39/c5/325462-sdm-vol-1-2abcd-3abcd.pdf
.p2align 2                                # force 4 byte alignment
gdt:
  SEG_NULLASM                             # null seg，gdt的地一个descriptor不用
  SEG_ASM(STA_X|STA_R, 0x0, 0xffffffff)   # code seg
  SEG_ASM(STA_W, 0x0, 0xffffffff)         # data seg

gdtdesc:
  .word   (gdtdesc - gdt - 1)             # sizeof(gdt) - 1，gdt的地址范围是[base, base+limit]，包含最后一个字节，所以减1
  .long   gdt                             # address gdt

